package com.inkegz.highlight

import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.view.View
import android.widget.Toast
import com.google.gson.Gson
import com.google.gson.annotations.JsonAdapter
import com.google.gson.annotations.SerializedName
import com.google.gson.internal.LinkedTreeMap
import com.inkegz.highlight.utils.*
import com.meelive.ingkee.logger.IKLog
import java.util.concurrent.TimeoutException

typealias HighlightActionBuilder = (String) -> HighlightAction<*>?

typealias HighlightGroupSuspendForBlock = suspend (HighlightAction<*>) -> Unit

const val ACTION_COLOR = "color"

const val ACTION_ROUTER = "router"

const val ACTION_REPLACE = "replace"

const val ACTION_IMAGE = "img"

const val DEFAULT_TIMEOUT = 300L

val defaultActionBuilder: HighlightActionBuilder = { actionName ->
    val replaceAction = DefaultReplaceAction()
    when (actionName) {
        ACTION_ROUTER -> DefaultRouteAction()
        ACTION_IMAGE -> DefaultImageAction()
        ACTION_COLOR -> DefaultColorAction()
        ACTION_REPLACE -> replaceAction
        else -> null
    }
}

/**
 * 使用
 * val ssb = withTimeout(DEFAULT_TIMEOUT) { highlightAnalysis(textContent.content, textContent.highlights) }
 * textView.text = ssb
 * textView.movementMethod = LinkMovementMethod.getInstance()
 */
suspend fun highlightAnalysis(
        content: String,
        highlightData: List<HighlightBean>,
        actionBuilder: HighlightActionBuilder = defaultActionBuilder
): CharSequence {
    val spanBuilder = SpannableStringBuilder(content)
    try {
        // 筛选 action
        var actionGroupList = highlightData.mapNotNull { highlightBean ->
            val ranges = transformRanges(highlightBean.ranges)
            val (success, actions) = actionCheck(highlightBean.actions, actionBuilder, ranges)
            if (!success) {
                val (baseSuccess, baseActions) = actionCheck(highlightBean.baseActions
                        ?: emptyList(), actionBuilder, ranges)
                if (!baseSuccess)
                    null
                else
                    baseActions
            } else
                actions
        }
        if (actionGroupList.isEmpty()) return spanBuilder
        // 排序 action
        actionGroupList = actionGroupList.sortedBy { actionGroup ->
            actionGroup.maxBy(HighlightAction<*>::priority)?.priority ?: -1
        }
        // 执行 action
        actionGroupList.suspendForBlock { action ->
            action.execute(spanBuilder)
        }
    } catch (time: TimeoutException) {
        return spanBuilder
    }
    return spanBuilder
}

private fun transformRanges(ranges: List<List<Int>>) =
        ranges.map { range ->
            if (range.size > 1 && checkRange(range))
                range[0] to range[1] + 1
            else
                -1 to -1
        }.filter { (f, s) -> f != -1 && s != -1 }

private fun checkRange(range: List<Int>): Boolean {
    val (start, end) = range
    return start <= end
}

private fun actionCheck(
        actionList: List<ActionBean>,
        actionBuilder: HighlightActionBuilder,
        ranges: List<Pair<Int, Int>>
): Pair<Boolean, List<HighlightAction<*>>> {
    if (!actionGroupCheck(actionList)) return false to emptyList()
    val actions = actionList.map { actionBean ->
        val action = actionBuilder(actionBean.name) ?: return false to emptyList()
        action.apply {
            actionParamJson = actionBean.param
            actionRanges = ranges
        }
    }
    actions.forEach { highlightAction ->
        highlightAction.check().isFalse { return false to emptyList() }
    }
    return true to actions
}

private fun actionGroupCheck(actionList: List<ActionBean>): Boolean {
    val actionsName = actionList.map(ActionBean::name)
    return when {
        // 图片 action 不能和 颜色action、替换action、跳转action 并存
        actionsName.contains(ACTION_IMAGE)
                && (actionsName.contains(ACTION_COLOR)
                || actionsName.contains(ACTION_REPLACE)
                || actionsName.contains(ACTION_ROUTER)) -> false
        else -> true
    }
}

private suspend fun List<List<HighlightAction<*>>>.suspendForBlock(block: HighlightGroupSuspendForBlock) {
    forEach { actionGroup ->
        actionGroup.forEach { action ->
            block(action)
        }
    }
}

data class HighlightBean(
        val actions: List<ActionBean> = emptyList(),
        val baseActions: List<ActionBean>? = null,
        val ranges: List<List<Int>>
)

data class ActionBean(
        val name: String,
        @JsonAdapter(RawJsonAdapter::class)
        val param: String
)

abstract class HighlightAction<T : ActionParamBean>(private val clazz: Class<T>, val priority: Int) {

    var actionParamJson: String? = null

    var actionBean: T? = null

    var actionRanges: List<Pair<Int, Int>>? = null

    fun check(): Boolean {
        val actionParamS = actionParamJson ?: return false
        val paramList = fromJson<LinkedTreeMap<String, Any>>(actionParamS) ?: return false
        val fields = clazz.declaredFields.map { field -> field.name }
        val fieldsCheck = paramList.keys.map { fields.contains(it) }.none { !it }
        return if (fieldsCheck) {
            (Gson().fromJson(actionParamS, clazz) ?: return false).let { bean ->
                checkParam(bean).isTrue { actionBean = bean }
            }
        } else {
            false
        }
    }

    /**
     * 检查参数操作
     */
    abstract fun checkParam(param: T): Boolean

    suspend fun execute(builder: SpannableStringBuilder) {
        if (null == actionBean || null == actionRanges) {
            IKLog.e("HighlightAction execute actionBean: $actionBean, ranges: $actionRanges")
            return
        }
        actionBean?.also { bean ->
            actionRanges?.also { range ->
                executeAction(builder, bean, range)
            }
        }
    }

    /**
     * 执行 action
     */
    abstract suspend fun executeAction(builder: SpannableStringBuilder, actionBean: T, ranges: List<Pair<Int, Int>>)

}

interface ActionParamBean

class DefaultRouteAction : HighlightAction<DefaultRouteActionBean>(DefaultRouteActionBean::class.java, 0) {
    override fun checkParam(param: DefaultRouteActionBean): Boolean {
        val url = param.links
        return true
    }

    override suspend fun executeAction(builder: SpannableStringBuilder, actionBean: DefaultRouteActionBean, ranges: List<Pair<Int, Int>>) {
        ranges.forEach { (start, end) ->
            builder.setSpan(object : ClickableSpan() {
                override fun onClick(widget: View) {
                    Toast.makeText(widget.context, "item click ${actionBean.links}", Toast.LENGTH_SHORT).show()
                }

                override fun updateDrawState(ds: TextPaint) {}
            }, start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
        }
    }
}

data class DefaultRouteActionBean(@SerializedName("links") val links: String) : ActionParamBean

class DefaultImageAction : HighlightAction<DefaultImageActionBean>(DefaultImageActionBean::class.java, 1) {
    override fun checkParam(param: DefaultImageActionBean): Boolean {
        if (param.url.isBlank()) return false
        if (param.width < 1) return false
        return true
    }

    override suspend fun executeAction(builder: SpannableStringBuilder, actionBean: DefaultImageActionBean, ranges: List<Pair<Int, Int>>) {
        ranges.forEach { (start, end) ->
            builder.setSpan(
                    getFrescoImageSpan(actionBean.url, actionBean.width.dp),
                    start,
                    end,
                    SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }
}

data class DefaultImageActionBean(val url: String, val width: Int) : ActionParamBean

class DefaultColorAction : HighlightAction<DefaultColorActionBean>(DefaultColorActionBean::class.java, 0) {
    override fun checkParam(param: DefaultColorActionBean): Boolean {
        val color = try {
            Color.parseColor(param.textColor)
        } catch (e: IllegalArgumentException) {
            0
        }
        return color != 0
    }

    override suspend fun executeAction(builder: SpannableStringBuilder, actionBean: DefaultColorActionBean, ranges: List<Pair<Int, Int>>) {
        ranges.forEach { (start, end) ->
            builder.setSpan(
                    ForegroundColorSpan(Color.parseColor(actionBean.textColor)),
                    start,
                    end,
                    SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }
}

data class DefaultColorActionBean(@SerializedName("textColor") val textColor: String) : ActionParamBean

/**
 * 特别说明
 * 本 action 需要保证自身是优先级最高的
 * 并且 对象 是单个解析操作中唯一的
 */
class DefaultReplaceAction : HighlightAction<DefaultReplaceActionBean>(DefaultReplaceActionBean::class.java, 2) {

    private var totalTime = 0

    private var executeTime = 0

    private val replaceDataList = ArrayList<ReplaceData>()

    override fun checkParam(param: DefaultReplaceActionBean): Boolean {
        return param.text.isNotBlank().isTrue {
            getRangeChange(param.text)
            totalTime++
        }
    }


    override suspend fun executeAction(
            builder: SpannableStringBuilder,
            actionBean: DefaultReplaceActionBean,
            ranges: List<Pair<Int, Int>>
    ) {
        executeTime++
        if (executeTime == totalTime)
            doReplace(builder)
    }

    private fun getRangeChange(text: String) {
        actionRanges?.also { ranges ->
            ranges.forEach { (start, end) ->
                replaceDataList.add(ReplaceData(start, end, text))
            }
        }
    }

    private fun doReplace(builder: SpannableStringBuilder) {
        val list = replaceDataList.sortedByDescending(ReplaceData::start)
        list.forEach { replaceData ->
            builder.replace(replaceData.start, replaceData.end, replaceData.text)
        }
    }

    data class ReplaceData(var start: Int, var end: Int, val text: String)
}

data class DefaultReplaceActionBean(@SerializedName("text") val text: String) : ActionParamBean


